/*
 * vector.h
 *
 * Copyright (C) 2018 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __MONOLITHIUM_VECTOR_H__
#define __MONOLITHIUM_VECTOR_H__

#include "defs.h"
#include "list.h"

#ifndef VECTOR_REALLOC
extern void *realloc(void*, size_t);
#define VECTOR_REALLOC(p, s) realloc((p), (s))
#endif

typedef struct
{
    void *address;
    size_t length;
} vector_buffer_t;

typedef struct
{
    list_entry_t link;
    vector_buffer_t buf;
} vector_buffer_entry_t;

typedef struct
{
    size_t position;
    size_t size;
    list_entry_t buffer_list;
} vector_t;

static bool_t vector_insert_buffer(vector_t *vector, size_t position, const vector_buffer_t *buffer)
{
    if (!buffer->length) return TRUE;
    list_entry_t *ptr = &vector->buffer_list;

    while (position)
    {
        list_entry_t *next = ptr->next;
        if (next == &vector->buffer_list) return FALSE;

        vector_buffer_entry_t *entry = CONTAINER_OF(next, vector_buffer_entry_t, link);
        if (entry->buf.length > position)
        {
            vector_buffer_entry_t *remaining = VECTOR_REALLOC(NULL, sizeof(vector_buffer_entry_t));
            if (!remaining) return FALSE;

            remaining->buf.address = (void*)((uintptr_t)entry->buf.address + position);
            remaining->buf.length = entry->buf.length - position;
            entry->buf.length = position;

            list_put_after(next, &remaining->link);
        }

        position -= entry->buf.length;
        ptr = next;
    }

    vector_buffer_entry_t *entry = VECTOR_REALLOC(NULL, sizeof(vector_buffer_entry_t));
    entry->buf = *buffer;
    list_put_after(ptr, &entry->link);
    vector->size += buffer->length;
    return TRUE;
}

static inline void vector_clear(vector_t *vector)
{
    vector->position = vector->size = 0;

    while (vector->buffer_list.next != &vector->buffer_list)
    {
        list_entry_t *ptr = vector->buffer_list.next;
        list_remove(ptr);
        ptr = VECTOR_REALLOC(ptr, 0);
    }
}

static inline bool_t vector_init(vector_t *vector, vector_buffer_t *buffers, size_t num_buffers)
{
    vector->position = vector->size = 0;
    list_init(&vector->buffer_list);

    int i;
    for (i = 0; i < num_buffers; i++)
    {
        if (!vector_insert_buffer(vector, vector->size, &buffers[i]))
        {
            vector_clear(vector);
            return FALSE;
        }
    }

    return TRUE;
}

static inline void vector_read_gather(vector_t *vector, void *data, size_t size)
{
    size_t position = 0;
    list_entry_t *ptr;

    for (ptr = vector->buffer_list.next; ptr != &vector->buffer_list && position < vector->position + size; ptr = ptr->next)
    {
        vector_buffer_t *buffer = &CONTAINER_OF(ptr, vector_buffer_entry_t, link)->buf;
        uintptr_t start = position > vector->position ? position : vector->position;
        uintptr_t end = position + buffer->length < vector->position + size ? position + buffer->length : vector->position + size;

        if (start < end)
        {
            __builtin_memcpy((void*)((uintptr_t)data + start - vector->position),
                             (void*)((uintptr_t)buffer->address + start - position),
                             end - start);
        }

        position += buffer->length;
    }

    vector->position += size;
}

static inline void vector_write_scatter(vector_t *vector, const void *data, size_t size)
{
    size_t position = 0;
    list_entry_t *ptr;

    for (ptr = vector->buffer_list.next; ptr != &vector->buffer_list && position < vector->position + size; ptr = ptr->next)
    {
        vector_buffer_t *buffer = &CONTAINER_OF(ptr, vector_buffer_entry_t, link)->buf;
        uintptr_t start = position > vector->position ? position : vector->position;
        uintptr_t end = position + buffer->length < vector->position + size ? position + buffer->length : vector->position + size;

        if (start < end)
        {
            __builtin_memcpy((void*)((uintptr_t)buffer->address + start - position),
                             (void*)((uintptr_t)data + start - vector->position),
                             end - start);
        }

        position += buffer->length;
    }

    vector->position += size;
}

static inline void vector_seek(vector_t *vector, size_t position)
{
    vector->position = position < vector->size ? position : vector->size;
}

#endif
